package de.slothsoft.wasbinich.tools.internal;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

public class CsvParser<T> implements Parser<T> {

    private static final String LINE_BREAK = System.getProperty("line.separator");

    private final Class<T> resultClass;

    private String separator = ",";

    public CsvParser(Class<T> resultClass) {
	super();
	this.resultClass = resultClass;
    }

    @Override
    public String format(List<T> input) throws ParserException {
	Field[] allFields = getDeclaredFields();
	StringBuilder sb = new StringBuilder();
	formatHeader(sb, allFields);
	for (T element : input) {
	    for (int i = 0; i < allFields.length; i++) {
		if (i > 0) {
		    sb.append(this.separator);
		}
		try {
		    sb.append(allFields[i].get(element));
		} catch (Exception e) {
		    throw new ParserException(e);
		}
	    }
	    sb.append(LINE_BREAK);
	}
	return sb.toString();
    }

    private void formatHeader(StringBuilder sb, Field[] allFields) {
	for (int i = 0; i < allFields.length; i++) {
	    if (i > 0) {
		sb.append(this.separator);
	    }
	    sb.append(allFields[i].getName());
	}
	sb.append(LINE_BREAK);
    }

    private Field[] getDeclaredFields() {
	Field[] result = this.resultClass.getDeclaredFields();
	for (Field field : result) {
	    field.setAccessible(true);
	}
	return result;
    }

    @Override
    public List<T> parse(String input) throws ParserException {
	String[] lines = input.split("(\n\r|\r\n|\n|\r)");
	if (lines.length < 2)
	    return new ArrayList<>();
	Field[] usedFields = findUsedFields(this.resultClass, lines[0].split(this.separator));
	return doParse(lines, this.resultClass, usedFields);
    }

    private List<T> doParse(String[] lines, Class<T> resultClass, Field[] usedFields) throws ParserException {
	List<T> result = new ArrayList<>();
	for (int i = 1; i < lines.length; i++) {
	    String line = lines[i];
	    if (!line.isEmpty()) {
		result.add(parseLine(line, resultClass, usedFields));
	    }
	}
	return result;
    }

    private <R> R parseLine(String line, Class<R> resultClass, Field[] usedFields) throws ParserException {
	try {
	    R result = resultClass.newInstance();
	    String[] cells = line.split(this.separator);
	    int minLength = Math.min(cells.length, usedFields.length);
	    for (int i = 0; i < minLength; i++) {
		if (usedFields[i] != null) {
		    usedFields[i].set(result, cells[i]);
		}
	    }
	    return result;
	} catch (Exception e) {
	    throw new ParserException(e);
	}
    }

    public Field[] findUsedFields(Class<?> resultClass, String[] header) {
	Field[] result = new Field[header.length];
	for (int i = 0; i < result.length; i++) {
	    result[i] = findField(resultClass, header[i]);
	}
	return result;
    }

    private Field findField(Class<?> resultClass, String string) {
	try {
	    Field field = resultClass.getDeclaredField(string);
	    field.setAccessible(true);
	    return field;
	} catch (Exception e) {
	    return null;
	}
    }

    public String getSeparator() {
	return this.separator;
    }

    public void setSeparator(String separator) {
	this.separator = separator;
    }

    public CsvParser<T> separator(String separator) {
	this.separator = separator;
	return this;
    }

}
